/**
 * 
 */
package jmat.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.Map.Entry;

import jmat.intr.Matrix;

/**
 * @author Aram Altschudjian
 *
 */

public class MatrixJC implements Matrix{
	private int dimensions = 0;
	private ArrayList<Integer> sizes = null;
	private ArrayList<Object> data = null;
	private ArrayList<HashMap<String, Integer>> names = null;
	private int entries = 0;
	private Object defaultValue = 0;
	
	public MatrixJC(){
		
	}
	
	public MatrixJC(Integer... sizes){
		setSize(sizes);
		init(defaultValue);
	}
	
	public MatrixJC(ArrayList<Object> data){
		assign(data);
	}
	
	public MatrixJC(ArrayList<Object> data, ArrayList<Integer> sizes){
		this.data = data;
		setSize(sizes);
	}
	
	public MatrixJC(ArrayList<Object> data, Integer... sizes){
		this.data = data;
		setSize(sizes);
	}

	public void init(Object defaultValue, ArrayList<Integer> sizes){
		setSize(sizes);
		init(defaultValue);
	}
	
	public void init(Object defaultValue, Integer... sizes){
		setSize(sizes);
		init(defaultValue);
	}
	
	public void init(Object defaultValue){
		validate();
		this.defaultValue = defaultValue;
		entries = 0;
		data = new ArrayList<Object>(size(0));
		fill(0, data);
	}
	
	private void fill(int dimension, ArrayList<Object> data) {
		if(dimension + 1 == dimensions){
			for(int i = data.size(); i < size(dimension); ++i){
				data.add(defaultValue);
			}
		} else if(dimension + 1 < dimensions){
			for(int i = data.size(); i < size(dimension); ++i){
				ArrayList<Object> next = new ArrayList<Object>(size(dimension + 1));
				fill(dimension + 1, next);
				data.add(next);
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
	}
	
	public void setDefault(Object defaultValue){
		this.defaultValue = defaultValue;
	}
	
	@SuppressWarnings("unchecked")
	public void data(Object data){
		if(data instanceof ArrayList){
			this.data = (ArrayList<Object>)data;	
		} else if(data instanceof List){
			this.data = new ArrayList<Object>((List<Object>)data);
		} else{
			throw new IllegalArgumentException("Illegal data type.");
		}
	}
	
	public ArrayList<Object> data(){
		return data;
	}
	
	@SuppressWarnings("unchecked")
	public Object val(){
		if(entries() == 1){
			Object result = data;
			for(int i = 0; i < dimensions; ++i){
				result = ((ArrayList<Object>)result).get(0);
			}
			return result;
		} else{
			throw new UnsupportedOperationException("Value request for dimensions = " + dimensions + " and sizes " + size() + " not supported.");
		}
	}
	
	public boolean valid(){
		if(dimensions == 0 || sizes == null || sizes.size() != dimensions){
			return false;
		}
		return true;
	}
	
	public void reset(){
		dimensions = 0;
		sizes = null;
		data = null;
		names = null;
		entries = 0;
	}
	
	public int entries(){
		if(entries == 0){
			entries = 1;
			for(int i = 0; i < dimensions; ++i){
				entries *= sizes.get(i);
			}
		}
		return entries;
	}
	
	public ArrayList<Integer> indices(){
		ArrayList<Integer> indices = new ArrayList<Integer>(entries());
		for(int i = 0; i < entries(); ++i){
			indices.add(i);
		}
		return indices;
	}
	
	@SuppressWarnings("unchecked")
	public void assign(Object data){
		data(data);
		ArrayList<Integer> sizes = new ArrayList<Integer>();
		if(data != null){
			while(data instanceof ArrayList){
				int size = ((ArrayList<Integer>)data).size();
				if(size > 0){
					sizes.add(size);
					data = ((ArrayList<Integer>)data).get(0);
					if(data == null){
						break;
					}
				} else{
					break;
				}
			}
		}
		setSize(sizes);
	}
	
	public void assign(Matrix matrix){
		if(!(matrix instanceof MatrixJC)){
			throw new IllegalArgumentException("Assignment of different Matrix types is currently not supported.");
		} else{
			MatrixJC input = (MatrixJC)matrix;
			data(input.data());
			setSize(input.size());
		}
	}
	
	public Matrix get(Object... address){
		if(address.length == 1){
			return get(address[0]);
		} else{
			return get((Object)address);
		}
	}
	
	@SuppressWarnings("unchecked")
	public Matrix get(Object address){
		if(address instanceof List){
			// Multiple 1D-coordinates
			List<Object> list = (List<Object>)address;
			ArrayList<Object> results = new ArrayList<Object>(list.size());
			for(int i = 0; i < list.size(); ++i){
				results.add(fetch(new Address(this, list.get(i)), 0, data));
			}
			return new MatrixJC(results, results.size());
		} else{
			// One or more multi-dim coordinates
			Address jAddress = new Address(this, address);
			return new MatrixJC(assemble(jAddress, 0, data), jAddress.size());
		}
	}
	
	@SuppressWarnings("unchecked")
	private Object fetch(Address address, int dimension, ArrayList<Object> data) {
		if(dimension + 1 == dimensions){
			return data.get(address.get(dimension, 0));
		} else if(dimension + 1 < dimensions){
			return fetch(address, dimension + 1, (ArrayList<Object>)data.get(address.get(dimension, 0)));
		} else{
			throw new IllegalArgumentException("Dimension mismatch.");
		}
	}

	@SuppressWarnings("unchecked")
	private ArrayList<Object> assemble(Address address, int dimension, ArrayList<Object> data) {
		ArrayList<Object> result = new ArrayList<Object>(address.size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < address.size(dimension); ++i){
				result.add(data.get(address.get(dimension, i)));
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < address.size(dimension); ++i){
				result.add(assemble(address, dimension + 1, (ArrayList<Object>)data.get(address.get(dimension, i))));
			}
		} else{
			throw new IllegalArgumentException("Dimension mismatch.");
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public void put(Object address, Object entry){
		Address jAddress = new Address(this, address);
		// Ensure capacity
		int[] maxima = jAddress.max();
		for(int i = 0; i < dimensions; ++i){
			int diff = maxima[i] - size(i) + 1;
			if(diff > 0){
				extend(i, diff);
			}
		}
		// Switching by entry type
		if(entry instanceof Matrix){
			// Multiple entries
			List<Object> list = ((Matrix)entry).list();
			// Make sure sizes match
			if(list.size() != jAddress.entries()){
				throw new IllegalArgumentException("Assignment size mismatch.");
			}
			// Distribute entries among addresses
			distribute(jAddress, 0, list, data);
		} else if(entry instanceof List){
			// Multiple entries
			List<Object> list = (List<Object>)entry;
			// Make sure sizes match
			if(list.size() != jAddress.entries()){
				throw new IllegalArgumentException("Assignment size mismatch.");
			}
			// Distribute entries among addresses
			distribute(jAddress, 0, list, data);
		} else{
			// Single entry
			place(jAddress, 0, entry, data);
		}
	}
	
	@SuppressWarnings("unchecked")
	private void place(Address address, int dimension, Object value, ArrayList<Object> data) {
		if(dimension + 1 == dimensions){
			data.set(address.get(dimension, 0), value);
		} else if(dimension + 1 < dimensions){
			place(address, dimension + 1, value, (ArrayList<Object>)data.get(address.get(dimension, 0)));
		} else{
			throw new IllegalArgumentException("Dimension mismatch.");
		}
	}

	@SuppressWarnings("unchecked")
	private void distribute(Address address, int dimension, List<Object> values, ArrayList<Object> data) {
		if(dimension + 1 == dimensions){
			if(address.size(dimension) != values.size()){
				throw new RuntimeException("Program logic error.");
			}
			for(int i = 0; i < address.size(dimension); ++i){
				data.set(address.get(dimension, i), values.get(i));
			}
		} else if(dimension + 1 < dimensions){
			int clusterSize = values.size() / address.size(dimension);
			int fromIndex = 0;
			int toIndex = clusterSize;
			for(int i = 0; i < address.size(dimension); ++i){
				distribute(address, dimension + 1, values.subList(fromIndex, toIndex), (ArrayList<Object>)data.get(address.get(dimension, i)));
				fromIndex = toIndex;
				toIndex += clusterSize;
			}
		} else{
			throw new IllegalArgumentException("Dimension mismatch.");
		}
	}
	
	public ArrayList<Integer> find(Object entry){
		ArrayList<Integer> results = new ArrayList<Integer>();
		find(0, new ArrayList<Integer>(), data, entry, results);
		return results;
	}
	
	@SuppressWarnings("unchecked")
	private void find(int dimension, ArrayList<Integer> address, ArrayList<Object> data, Object entry, ArrayList<Integer> results) {
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(i); ++i){
				if(data.get(i).equals(entry)){
					ArrayList<Integer> match = new ArrayList<Integer>(address);
					match.add(i);
					results.add(convert(match));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(i); ++i){
				ArrayList<Integer> next = new ArrayList<Integer>(address);
				next.add(i);
				find(dimension + 1, next, (ArrayList<Object>)data.get(i), entry, results);
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
	}
	
	public ArrayList<Integer> nfind(Object entry){
		ArrayList<Integer> results = new ArrayList<Integer>();
		nfind(0, new ArrayList<Integer>(), data, entry, results);
		return results;
	}
	
	@SuppressWarnings("unchecked")
	private void nfind(int dimension, ArrayList<Integer> address, ArrayList<Object> data, Object entry, ArrayList<Integer> results) {
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(i); ++i){
				if(!data.get(i).equals(entry)){
					ArrayList<Integer> match = new ArrayList<Integer>(address);
					match.add(i);
					results.add(convert(match));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(i); ++i){
				ArrayList<Integer> next = new ArrayList<Integer>(address);
				next.add(i);
				find(dimension + 1, next, (ArrayList<Object>)data.get(i), entry, results);
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
	}
	
	public int count(Object entry){
		return count(0, data, entry);
	}
	
	@SuppressWarnings("unchecked")
	private int count(int dimension, ArrayList<Object> data, Object entry) {
		int counter = 0;
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(data.get(i).equals(entry)){
					++counter;
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				counter += count(dimension + 1, (ArrayList<Object>)data.get(i), entry);
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
		return counter;
	}

	public void setDim(int dimensions){
		this.dimensions = dimensions;
		if(names == null){
			names = new ArrayList<HashMap<String, Integer>>(dimensions);
		} else{
			names.ensureCapacity(dimensions);
		}
	}
	
	public int getDim(){
		return dimensions;
	}
	
	public void setSize(ArrayList<Integer> sizes){
		this.sizes = sizes;
		setDim(this.sizes.size());
		entries = 0;
	}
	
	public void setSize(Integer... sizes){
		this.sizes = new ArrayList<Integer>(sizes.length);
		for(int i = 0; i < sizes.length; ++i){
			this.sizes.add(sizes[i]);
		}
		setDim(this.sizes.size());
		entries = 0;
	}
	
	public ArrayList<Integer> size(){
		return sizes;
	}
	
	public int size(int dimension){
		return sizes.get(dimension);
	}
	
	public ArrayList<Object> list(){
		ArrayList<Object> list = new ArrayList<Object>(entries());
		for(int i = 0; i < entries(); ++i){
			list.add(get(i).val());
		}
		return list;
	}
	
	public ArrayList<Object> unique(){
		TreeSet<Object> set = new TreeSet<Object>(list());
		Iterator<Object> iterator = set.iterator();
		ArrayList<Object> result = new ArrayList<Object>(set.size());
		while(iterator.hasNext()){
			result.add(iterator.next());
		}
		return result;
	}
	
	public boolean isEmpty(){
		return entries() == 0;
	}
	
	public boolean equals(MatrixJC candidate){
		return data.equals(candidate.data());
	}
	
	public boolean equals(Matrix candidate){
		if(candidate instanceof MatrixJC){
			return equals((MatrixJC)candidate);
		}
		boolean isEqual = entries() == candidate.entries() && size().equals(candidate.size());
		int i = 0;
		while(isEqual && i < entries()){
			isEqual = get(i).val().equals(candidate.get(i).val());
			++i;
		}
		return isEqual;
	}
	
	public Object max(){
		Double result = max(0, data);
		if(result.intValue() == result.doubleValue()){
			return result.intValue();
		} else{
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private Double max(int dimension, ArrayList<Object> data){
		Double result = Double.NEGATIVE_INFINITY;
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(data.get(i) instanceof Number){
					result = java.lang.Math.max(result, ((Number)data.get(i)).doubleValue());
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result = java.lang.Math.max(result, max(dimension + 1, (ArrayList<Object>)data.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public int maxIndex(){
		// This is not a performance-optimal implementation
		ArrayList<Object> elements = list();
		Double max = Double.NEGATIVE_INFINITY;
		int index = 0;
		Object element;
		for(int i = 0; i < entries(); ++i){
			element = elements.get(i);
			if(element instanceof Number && ((Number)element).doubleValue() > max){
				max = ((Number)element).doubleValue();
				index = i;
			}
		}
		return index;
	}
	
	public Object min(){
		Double result = min(0, data);
		if(result.intValue() == result.doubleValue()){
			return result.intValue();
		} else{
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private Double min(int dimension, ArrayList<Object> data){
		Double result = Double.POSITIVE_INFINITY;
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(data.get(i) instanceof Number){
					result = java.lang.Math.min(result, ((Number)data.get(i)).doubleValue());
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result = java.lang.Math.min(result, min(dimension + 1, (ArrayList<Object>)data.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public int minIndex(){
		// This is not a performance-optimal implementation
		ArrayList<Object> elements = list();
		Double min = Double.POSITIVE_INFINITY;
		int index = 0;
		Object element;
		for(int i = 0; i < entries(); ++i){
			element = elements.get(i);
			if(element instanceof Number && ((Number)element).doubleValue() < min){
				min = ((Number)element).doubleValue();
				index = i;
			}
		}
		return index;
	}
	
	public Matrix sum(){
		if(dimensions == 2){
			return new MatrixJC(twoDSum());
		} else if(dimensions == 1){
			ArrayList<Object> sum = new ArrayList<Object>(1);
			sum.add(sumAll());
			return new MatrixJC(sum);
		} else{
			throw new UnsupportedOperationException("Currently can only form sum for 1- and 2-dimensional matrices.");
		}
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> twoDSum(){
		ArrayList<Object> result = new ArrayList<Object>(size(1));
		for(int i = 0; i < size(1); ++i){
			Double sum = 0.0;
			Object element;
			for(int j = 0; j < size(0); ++j){
				element = ((ArrayList<Object>)data.get(j)).get(i);
				if(element instanceof Number){
					sum += ((Number)element).doubleValue();
				}
			}
			if(sum.intValue() == sum.doubleValue()){
				result.add(sum.intValue());
			} else{
				result.add(sum);
			}
		}
		return result;
	}
	
	public Object sumAll(){
		Double result = sumAll(0, data);
		if(result.intValue() == result.doubleValue()){
			return result.intValue();
		} else{
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private Double sumAll(int dimension, ArrayList<Object> data){
		Double result = 0.0;
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(data.get(i) instanceof Number){
					result += ((Number)data.get(i)).doubleValue();
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result += sumAll(dimension + 1, (ArrayList<Object>)data.get(i));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix plus(Matrix matrix){
		if(!(matrix instanceof MatrixJC)){
			throw new IllegalArgumentException("Currently can only add matrices of the same implementation type.");
		} else if(!size().equals(matrix.size())){
			throw new IllegalArgumentException("Size mismatch between " + size() + " and " + matrix.size());
		} else{
			MatrixJC result = new MatrixJC(plus(0, data(), ((MatrixJC)matrix).data()));
			result.names = names;
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> plus(int dimension, ArrayList<Object> own, ArrayList<Object> other){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number && other.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() + ((Number)other.get(i)).doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(plus(dimension + 1, (ArrayList<Object>)own.get(i), (ArrayList<Object>)other.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix plus(Number number){
		MatrixJC result = new MatrixJC(plus(0, data(), number));
		result.names = names;
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> plus(int dimension, ArrayList<Object> own, Number number){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() + number.doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(plus(dimension + 1, (ArrayList<Object>)own.get(i), number));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix minus(Matrix matrix){
		if(!(matrix instanceof MatrixJC)){
			throw new IllegalArgumentException("Currently can only add matrices of the same implementation type.");
		} else if(!size().equals(matrix.size())){
			throw new IllegalArgumentException("Size mismatch between " + size() + " and " + matrix.size());
		} else{
			MatrixJC result = new MatrixJC(minus(0, data(), ((MatrixJC)matrix).data()));
			result.names = names;
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> minus(int dimension, ArrayList<Object> own, ArrayList<Object> other){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number && other.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() - ((Number)other.get(i)).doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(minus(dimension + 1, (ArrayList<Object>)own.get(i), (ArrayList<Object>)other.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix minus(Number number){
		MatrixJC result = new MatrixJC(minus(0, data(), number));
		result.names = names;
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> minus(int dimension, ArrayList<Object> own, Number number){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() - number.doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(minus(dimension + 1, (ArrayList<Object>)own.get(i), number));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix mult(Matrix matrix){
		if(!(matrix instanceof MatrixJC)){
			throw new IllegalArgumentException("Currently can only add matrices of the same implementation type.");
		} else if(!size().equals(matrix.size())){
			throw new IllegalArgumentException("Size mismatch between " + size() + " and " + matrix.size());
		} else{
			MatrixJC result = new MatrixJC(mult(0, data(), ((MatrixJC)matrix).data()));
			result.names = names;
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> mult(int dimension, ArrayList<Object> own, ArrayList<Object> other){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number && other.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() * ((Number)other.get(i)).doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(mult(dimension + 1, (ArrayList<Object>)own.get(i), (ArrayList<Object>)other.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix mult(Number number){
		MatrixJC result = new MatrixJC(mult(0, data(), number));
		result.names = names;
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> mult(int dimension, ArrayList<Object> own, Number number){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() * number.doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(mult(dimension + 1, (ArrayList<Object>)own.get(i), number));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix div(Matrix matrix){
		if(!(matrix instanceof MatrixJC)){
			throw new IllegalArgumentException("Currently can only add matrices of the same implementation type.");
		} else if(!size().equals(matrix.size())){
			throw new IllegalArgumentException("Size mismatch between " + size() + " and " + matrix.size());
		} else{
			MatrixJC result = new MatrixJC(div(0, data(), ((MatrixJC)matrix).data()));
			result.names = names;
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> div(int dimension, ArrayList<Object> own, ArrayList<Object> other){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number && other.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() / ((Number)other.get(i)).doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(div(dimension + 1, (ArrayList<Object>)own.get(i), (ArrayList<Object>)other.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix div(Number number){
		MatrixJC result = new MatrixJC(div(0, data(), number));
		result.names = names;
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> div(int dimension, ArrayList<Object> own, Number number){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number){
					Double calc = ((Number)own.get(i)).doubleValue() / number.doubleValue();
					if(calc.intValue() == calc.doubleValue()){
						result.add(calc.intValue());
					} else{
						result.add(calc.doubleValue());
					}
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(div(dimension + 1, (ArrayList<Object>)own.get(i), number));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix log(){
		MatrixJC result = new MatrixJC(log(0, data()));
		result.names = names;
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private ArrayList<Object> log(int dimension, ArrayList<Object> own){
		ArrayList<Object> result = new ArrayList<Object>(size(dimension));
		if(dimension + 1 == dimensions){
			for(int i = 0; i < size(dimension); ++i){
				if(own.get(i) instanceof Number){
					result.add(java.lang.Math.log(((Number)own.get(i)).doubleValue()));
				} else{
					result.add(own.get(i));
				}
			}
		} else if(dimension + 1 < dimensions){
			for(int i = 0; i < size(dimension); ++i){
				result.add(log(dimension + 1, (ArrayList<Object>)own.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException("Should not happen.");
		}
		return result;
	}
	
	public Matrix subFrom(Number minuend){
		MatrixJC result = new MatrixJC();
		result.init(minuend, size());
		result.names = names;
		return result.minus(this);
	}
	
	public void extend(int dimension){
		extend(dimension, 1);
	}
	
	public void extend(int dimension, int amount){
		sizes.set(dimension, sizes.get(dimension) + amount);
		enlarge(dimension, 0, data, amount);
		entries = 0;
	}
	
	@SuppressWarnings("unchecked")
	private void enlarge(int goal, int current, ArrayList<Object> data, int amount) {
		if(current == goal){
			data.ensureCapacity(data.size() + amount);
			fill(current, data);
		} else if(current < goal){
			for(int i = 0; i < size(current); ++i){
				enlarge(goal, current + 1, (ArrayList<Object>)data.get(i), amount);
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
	}

	@SuppressWarnings("unchecked")
	public void delete(Object address){
		if(isAll(address)){
			// Set all entries to 'null', but keep structure and name resolution.
			// For a total reset, use reset() instead.
			data = null;
			init(defaultValue);
		} else if(address instanceof ArrayList){
			entries = 0;
			ArrayList<Object> list = (ArrayList<Object>)address;
			if(list.size() != dimensions){
				throw new IllegalArgumentException("Dimension mismatch.");
			}
			for(int i = list.size() - 1; i >= 0; --i){
				if(!isAll(list.get(i))){
					delete(i, list.get(i));
				}
			}
		} else{
			throw new IllegalArgumentException("Address format mismatch.");
		}
	}
	
	public void delete(Object... address){
		entries = 0;
		if(address.length != dimensions){
			throw new IllegalArgumentException("Dimension mismatch.");
		}
		for(int i = address.length - 1; i >= 0; --i){
			if(!isAll(address[i])){
				delete(i, address[i]);
			}
		}
	}

	public void delete(int dimension, Object... address){
		delete(dimension, (Object)address);
	}
	
	@SuppressWarnings("unchecked")
	public void delete(int dimension, Object address){
		entries = 0;
		ArrayList<Integer> indices;
		if(isAll(address)){
			delete(all);
			return;
		} else if(address instanceof Integer){
			indices = new ArrayList<Integer>(1);
			int index = (Integer)address;
			indices.add(index < 0 ? size(dimension) + index : index);
		} else if(address instanceof String){
			indices = new ArrayList<Integer>(1);
			indices.add(resolve(dimension, (String)address));
		} else if(address instanceof Object[]){
			Object[] array = (Object[])address;
			indices = new ArrayList<Integer>(array.length);
			for(int i = 0; i < array.length; ++i){
				if(array[i] instanceof Integer){
					indices.add((Integer)array[i]);
				} else if(array[i] instanceof String){
					indices.add(resolve(dimension, (String)array[i]));
				} else{
					throw new IllegalArgumentException("Illegal index format.");
				}
			}
		} else if(address instanceof List){
			List<Object> list = (List<Object>)address;
			indices = new ArrayList<Integer>(list.size());
			for(int i = 0; i < list.size(); ++i){
				if(list.get(i) instanceof Integer){
					int index = (Integer)list.get(i);
					indices.add(index < 0 ? size(dimension) + index : index);
				} else if(list.get(i) instanceof String){
					indices.add(resolve(dimension, (String)list.get(i)));
				} else{
					throw new IllegalArgumentException("Illegal index format.");
				}
			}
		} else{
			throw new IllegalArgumentException("Illegal index format.");
		}
		purge(dimension, 0, indices, data);
	}
	
	@SuppressWarnings("unchecked")
	private void purge(int goal, int current, ArrayList<Integer> indices, ArrayList<Object> data) {
		if(current == goal){
			for(int i = 0; i < indices.size(); ++i){
				data.remove(indices.get(i));
				fixNames(goal, indices.get(i));
			}
			sizes.set(current, sizes.get(current) - indices.size());
		} else if(current < goal){
			for(int i = 0; i < size(current); ++i){
				purge(goal, current + 1, indices, (ArrayList<Object>)data.get(i));
			}
		} else{
			throw new IllegalArgumentException("Dimension mismatch.");
		}
	}

	private void fixNames(int dimension, int removed) {
		if(names == null || getNames(dimension) == null){
			return;
		}
		Iterator<Entry<String, Integer>> iterator = getNames(dimension).entrySet().iterator();
		while(iterator.hasNext()){
			Entry<String, Integer> entry = iterator.next();
			if(entry.getValue() == removed){
				iterator.remove();
			} else if(entry.getValue() > removed){
				entry.setValue(entry.getValue() - 1);
			}
		}
	}
	
	public void setOrder(int dimension, ArrayList<Integer> order){
		setOrder(dimension, order.toArray(new Integer[0]));
	}
	
	public void setOrder(int dimension, Integer... order){
		int size = size(dimension);
		if(order.length != size){
			throw new IllegalArgumentException("Please supply order indices for all elements of the dimension.");
		}
		int diff = 0;
		for(int i = 0; i < size; ++i){
			if(order[i] - size + 1 > diff){
				diff = order[i] - size + 1;
			}
		}
		if(diff > 0){
			extend(dimension, diff);
		}
		data = reorder(dimension, 0, order, data);
	}

	@SuppressWarnings("unchecked")
	private ArrayList<Object> reorder(int goal, int current, Integer[] order, ArrayList<Object> data) {
		ArrayList<Object> result = new ArrayList<Object>(size(current));
		if(current == goal){
			for(int i = 0; i < order.length; ++i){
				result.set(order[i], data.get(i));
			}
		} else if(current < goal){
			for(int i = 0; i < size(current); ++i){
				result.add(reorder(goal, current + 1, order, (ArrayList<Object>)data.get(i)));
			}
		} else{
			throw new IndexOutOfBoundsException();
		}
		return result;
	}

	public void addDim(int size, int index){
		entries = 0;
		ArrayList<Object> newData = new ArrayList<Object>(size);
		for(int i = 0; i < size; ++i){
			if(i == index){
				newData.add(data);
			} else{
				newData.add(null);
			}
		}
		++dimensions;
		ArrayList<Integer> newSizes = new ArrayList<Integer>(dimensions);
		newSizes.add(size);
		newSizes.addAll(sizes);
		data = newData;
		sizes = newSizes;
	}
	
	public void setName(int dimension, int index, String name){
		if(names == null){
			names = new ArrayList<HashMap<String,Integer>>(dimensions);
		}
		HashMap<String, Integer> map = names.get(dimension);
		if(map == null){
			map = new HashMap<String, Integer>(sizes.get(dimension));
			names.set(dimension, map);
		}
		map.put(name, index);
	}
	
	public void setNames(int dimension, HashMap<String, Integer> nameToIndexMap){
		if(names == null){
			names = new ArrayList<HashMap<String,Integer>>(dimensions);
		}
		HashMap<String, Integer> map = names.get(dimension);
		if(map == null){
			names.set(dimension, nameToIndexMap);
		} else{
			map.putAll(nameToIndexMap);
		}
	}
	
	public HashMap<String, Integer> getNames(int dimension){
		return names.get(dimension);
	}
	
	public int resolve(int dimension, String name){
		return names.get(dimension).get(name);
	}
	
	public boolean isAll(Object candidate) {
		if(candidate instanceof Integer){
			return (Integer)candidate == Matrix.all;
		}
		return false;
	}
	
	public Integer[] convert(int address){
		Integer[] coordinates = new Integer[dimensions];
		int div = 1;
		int mod = 1;
		for(int i = 0; i < dimensions; ++i){
			mod *= size(i);
			coordinates[i] = (address % mod) / div;
			div = mod;
		}
		return coordinates;
	}
	
	public int convert(ArrayList<Object> address){
		return convert(address.toArray());
	}
	
	public int convert(Object... address){
		Integer[] coordinates;
		if(address instanceof Integer[]){
			coordinates = (Integer[])address;
		} else{
			throw new IllegalArgumentException("Address format mismatch");
		}
		int index = 0;
		int dimensionSize = 1;
		for(int i = 0; i < coordinates.length; ++i){
			index += (coordinates[i] < 0 ? (size(i) + coordinates[i]) * dimensionSize : coordinates[i] * dimensionSize);
			dimensionSize *= size(i);
		}
		return index;
	}
	
	private void validate(){
		if(!valid()){
			throw new RuntimeException("Inconsistent or uninitialized structure description.");
		}
	}
	
	public String toString(){
		if(data != null){
			return data.toString();
		} else{
			throw new RuntimeException("Requesting String representation of an uninitialized structure.");
		}
	}
}
